package com.nvwa.coding.util.safe;

import com.nvwa.coding.model.Opt;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.*;
import java.util.stream.Collectors;

public abstract class Transformer extends Checker {

    public static String trim(String s) {
        return nonNullThen(s, String::trim);
    }

    public static Optional<String> strOpt(Object o, boolean notBlank) {
        if (o == null) {
            return Optional.empty();
        }
        Optional<String> result;
        if (o instanceof String) {
            result = Optional.of((String) o);
        } else {
            switch (o.getClass().getTypeName()) {
                case "java.math.BigDecimal":
                    result = Optional.of((BigDecimal) o).map(b -> b.stripTrailingZeros().toPlainString());
                    break;
                default:
                    result = loggingGetOpt(() -> String.valueOf(o));
            }
        }
        return notBlank ? result.filter(Checker::notBlank) : result;
    }

    public static Opt<String> strOpt_(Object o, boolean notBlank) {
        return Opt.of(strOpt(o, notBlank));
    }

    public static Optional<String> strOpt(Object o) {
        return strOpt(o, true);
    }

    public static Opt<String> strOpt_(Object o) {
        return strOpt_(o, true);
    }

    public static Optional<String> stringOpt(Object o) {
        return strOpt(o, false);
    }

    public static Opt<String> stringOpt_(Object o) {
        return strOpt_(o, false);
    }

    public static String strOrNull(Object o) {
        return strOpt(o).orElse(null);
    }

    public static String stringOrNull(Object o) {
        return stringOpt(o).orElse(null);
    }

    public static String strOrEmpty(Object o) {
        return strOpt(o).orElse("");
    }

    @SuppressWarnings("unchecked")
    public static <T> Optional<T> parseOpt(String s, Class<T> clazz) {
        if (clazz == null) {
            return Optional.empty();
        }
        switch (clazz.getTypeName()) {
            case "java.lang.String":
                return (Optional<T>) stringOpt(s);
            case "java.lang.Boolean":
                return (Optional<T>) boolOpt(s);
            case "java.lang.Byte":
                return (Optional<T>) byteOpt(s);
            case "java.lang.Integer":
                return (Optional<T>) intOpt(s);
            case "java.lang.Long":
                return (Optional<T>) longOpt(s);
            case "java.lang.Float":
                return (Optional<T>) floatOpt(s);
            case "java.lang.Double":
                return (Optional<T>) doubleOpt(s);
            case "java.math.BigDecimal":
                return (Optional<T>) bigDecimalOpt(s);
            default:
                return Optional.empty();
        }
    }

    public static <T> Opt<T> parseOpt_(String s, Class<T> clazz) {
        return Opt.of(parseOpt(s, clazz));
    }

    public static <T> T parseOrNull(String s, Class<T> clazz) {
        return parseOpt(s, clazz).orElse(null);
    }

    public static Optional<Boolean> boolOpt(Object o) {
        return Optional.ofNullable(boolOrNull(o));
    }

    public static Opt<Boolean> boolOpt_(Object o) {
        return Opt.of(boolOrNull(o));
    }

    public static boolean boolOrFalse(Object o) {
        return Optional.ofNullable(boolOrNull(o)).orElse(false);
    }

    public static Boolean boolOrNull(Object o) {
        return loggingGetOrNull(() -> {
            if (o == null) {
                return null;
            }
            if (o instanceof Boolean) {
                return (Boolean) o;
            }
            if (o instanceof String) {
                String s = (String) o;
                if ("true".equalsIgnoreCase(s)
                        || "1".equalsIgnoreCase(s)
                        || "T".equalsIgnoreCase(s)
                        || "YES".equalsIgnoreCase(s)
                        || "Y".equalsIgnoreCase(s)
                        || "ON".equalsIgnoreCase(s)
                ) {
                    return true;
                }
                if ("false".equalsIgnoreCase(s)
                        || "0".equalsIgnoreCase(s)
                        || "F".equalsIgnoreCase(s)
                        || "NO".equalsIgnoreCase(s)
                        || "N".equalsIgnoreCase(s)
                        || "OFF".equalsIgnoreCase(s)
                ) {
                    return false;
                }
            }
            if (o instanceof Number) {
                if (((Number) o).intValue() == 1) {
                    return true;
                }
                if (((Number) o).intValue() == 0) {
                    return false;
                }
            }
            return null;
        });
    }

    public static Optional<Byte> byteOpt(String s) {
        return Optional.ofNullable(byteOrNull(s));
    }

    public static Opt<Byte> byteOpt_(String s) {
        return Opt.of(byteOrNull(s));
    }

    public static Byte byteOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Byte.parseByte(s)) : null;
    }

    public static byte byteOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Byte.parseByte(s), (byte) 0) : 0;
    }

    public static Optional<Short> shortOpt(String s) {
        return Optional.ofNullable(shortOrNull(s));
    }

    public static Opt<Short> shortOpt_(String s) {
        return Opt.of(shortOrNull(s));
    }

    public static Short shortOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Short.parseShort(s)) : null;
    }

    public static short shortOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Short.parseShort(s), (short) 0) : 0;
    }

    public static Optional<Integer> intOpt(String s) {
        return Optional.ofNullable(intOrNull(s));
    }

    public static Opt<Integer> intOpt_(String s) {
        return Opt.of(intOrNull(s));
    }

    public static Integer intOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Integer.parseInt(s)) : null;
    }

    public static int intOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Integer.parseInt(s), 0) : 0;
    }

    public static Optional<Integer> intOpt(Boolean b) {
        return Optional.ofNullable(intOrNull(b));
    }

    public static Opt<Integer> intOpt_(Boolean b) {
        return Opt.of(intOrNull(b));
    }

    public static Integer intOrNull(Boolean b) {
        return b == null ? null : b ? 1 : 0;
    }

    public static int intOrZero(Boolean b) {
        return intOpt(b).orElse(0);
    }

    public static Optional<Long> longOpt(String s) {
        return Optional.ofNullable(longOrNull(s));
    }

    public static Opt<Long> longOpt_(String s) {
        return Opt.of(longOrNull(s));
    }

    public static Long longOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Long.parseLong(s)) : null;
    }

    public static long longOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Long.parseLong(s), 0L) : 0;
    }

    public static Optional<Float> floatOpt(String s) {
        return Optional.ofNullable(floatOrNull(s));
    }

    public static Opt<Float> floatOpt_(String s) {
        return Opt.of(floatOrNull(s));
    }

    public static Float floatOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Float.parseFloat(s)) : null;
    }

    public static float floatOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Float.parseFloat(s), 0F) : 0;
    }

    public static Optional<Double> doubleOpt(String s) {
        return Optional.ofNullable(doubleOrNull(s));
    }

    public static Opt<Double> doubleOpt_(String s) {
        return Opt.of(doubleOrNull(s));
    }

    public static Double doubleOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> Double.parseDouble(s)) : null;
    }

    public static double doubleOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> Double.parseDouble(s), 0D) : 0;
    }

    public static Optional<BigDecimal> bigDecimalOpt(String s) {
        return Optional.ofNullable(bigDecimalOrNull(s));
    }

    public static Opt<BigDecimal> bigDecimalOpt_(String s) {
        return Opt.of(bigDecimalOrNull(s));
    }

    public static BigDecimal bigDecimalOrNull(String s) {
        return notBlank(s) ? loggingGetOrNull(() -> new BigDecimal(s)) : null;
    }

    public static BigDecimal bigDecimalOrZero(String s) {
        return notBlank(s) ? loggingGetOrElse(() -> new BigDecimal(s), BigDecimal.ZERO) : BigDecimal.ZERO;
    }

    public static Optional<BigDecimal> bigDecimalOpt(Number n) {
        return Optional.ofNullable(bigDecimalOrNull(n));
    }

    public static Opt<BigDecimal> bigDecimalOpt_(Number n) {
        return Opt.of(bigDecimalOrNull(n));
    }

    public static BigDecimal bigDecimalOrNull(Number n) {
        return notNull(n) ? loggingGetOrNull(() -> BigDecimal.valueOf(n.doubleValue())) : null;
    }

    public static BigDecimal bigDecimalOrZero(Number n) {
        return notNull(n) ? loggingGetOrElse(() -> BigDecimal.valueOf(n.doubleValue()), BigDecimal.ZERO) : BigDecimal.ZERO;
    }

    public static <E> E[] array(Collection<E> collection, E[] demo) {
        return Optional.ofNullable(collection).orElseGet(Collections::emptyList).toArray(demo);
    }

    @SafeVarargs
    public static <E> List<E> arrayList(E... array) {
        return stream(array).filter(Checker::notNull).collect(Collectors.toList());
    }

    public static <E> List<E> arrayList(Collection<E> collection) {
        return stream(collection).filter(Checker::notNull).collect(Collectors.toList());
    }

    public static <E, N> List<N> arrayList(E[] origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toList());
    }

    public static <E, N> List<N> arrayList(Collection<E> origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toList());
    }

    public static <K, V, N> List<N> arrayList(Map<K, V> originMap, BiFunction<K, V, N> mapper) {
        return stream(originMap).map(e -> mapper.apply(e.getKey(), e.getValue())).collect(Collectors.toList());
    }

    @SafeVarargs
    public static <E> List<E> linkedList(E... array) {
        return stream(array).filter(Checker::notNull).collect(Collectors.toCollection(LinkedList::new));
    }

    public static <E> List<E> linkedList(Collection<E> collection) {
        return stream(collection).filter(Checker::notNull).collect(Collectors.toCollection(LinkedList::new));
    }

    public static <E, N> List<N> linkedList(E[] origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toCollection(LinkedList::new));
    }

    public static <E, N> List<N> linkedList(Collection<E> origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toCollection(LinkedList::new));
    }

    public static <K, V, N> List<N> linkedList(Map<K, V> originMap, BiFunction<K, V, N> mapper) {
        return stream(originMap).map(e -> mapper.apply(e.getKey(), e.getValue())).collect(Collectors.toCollection(LinkedList::new));
    }

    @SafeVarargs
    public static <E> Set<E> hashSet(E... array) {
        return stream(array).filter(Checker::notNull).collect(Collectors.toSet());
    }

    public static <E> Set<E> hashSet(Collection<E> origin) {
        return stream(origin).filter(Checker::notNull).collect(Collectors.toSet());
    }

    public static <E, N> Set<N> hashSet(E[] origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toSet());
    }

    public static <E, N> Set<N> hashSet(Collection<E> origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toSet());
    }

    public static <K, V, N> Set<N> hashSet(Map<K, V> originMap, BiFunction<K, V, N> mapper) {
        return stream(originMap).map(e -> mapper.apply(e.getKey(), e.getValue())).collect(Collectors.toSet());
    }

    @SafeVarargs
    public static <E> Set<E> linkedSet(E... array) {
        return stream(array).filter(Checker::notNull).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    public static <E> Set<E> linkedSet(Collection<E> origin) {
        return stream(origin).filter(Checker::notNull).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    public static <E, N> Set<N> linkedSet(E[] origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    public static <E, N> Set<N> linkedSet(Collection<E> origin, Function<E, N> mapper) {
        return stream(origin).map(mapper).filter(Checker::notNull).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    public static <K, V, N> Set<N> linkedSet(Map<K, V> originMap, BiFunction<K, V, N> mapper) {
        return stream(originMap).map(e -> mapper.apply(e.getKey(), e.getValue())).collect(Collectors.toCollection(LinkedHashSet::new));
    }

    /**
     * 构造集合
     *
     * @param init     提供集合的初始化方式
     * @param elements 加入到集合中的元素
     * @param <T>      集合类型
     * @param <E>      集合元素类型
     * @return 集合
     */
    @SafeVarargs
    public static <T extends Collection<E>, E> T collection(Supplier<T> init, E... elements) {
        T collection = init.get();
        if (length(elements) > 0) {
            collection.addAll(Arrays.asList(elements));
        }
        return collection;
    }

    public static <T, K, V> Map<K, V> hashMap(T[] collection, Function<T, K> keyFun, Function<T, V> valueFun) {
        return stream(collection).collect(Collectors.toMap(keyFun, valueFun, (oldVal, newVal) -> newVal, HashMap::new));
    }

    public static <T, K, V> Map<K, V> hashMap(Collection<T> collection, Function<T, K> keyFun, Function<T, V> valueFun) {
        return stream(collection).collect(Collectors.toMap(keyFun, valueFun, (oldVal, newVal) -> newVal, HashMap::new));
    }

    public static <T extends Map<K1, V1>, K1, V1, K2, V2> Map<K2, V2> hashMap(T map, BiFunction<K1, V1, K2> keyFun, BiFunction<K1, V1, V2> valueFun) {
        return stream(map).collect(Collectors.toMap(
                e -> keyFun.apply(e.getKey(), e.getValue()),
                e -> valueFun.apply(e.getKey(), e.getValue()),
                (oldVal, newVal) -> newVal,
                HashMap::new
        ));
    }

    public static <T, K, V> Map<K, V> linkedMap(T[] collection, Function<T, K> keyFun, Function<T, V> valueFun) {
        return stream(collection).collect(Collectors.toMap(keyFun, valueFun, (oldVal, newVal) -> newVal, LinkedHashMap::new));
    }

    public static <T, K, V> Map<K, V> linkedMap(Collection<T> collection, Function<T, K> keyFun, Function<T, V> valueFun) {
        return stream(collection).collect(Collectors.toMap(keyFun, valueFun, (oldVal, newVal) -> newVal, LinkedHashMap::new));
    }

    public static <T extends Map<K1, V1>, K1, V1, K2, V2> Map<K2, V2> linkedMap(T map, BiFunction<K1, V1, K2> keyFun, BiFunction<K1, V1, V2> valueFun) {
        return stream(map).collect(Collectors.toMap(
                e -> keyFun.apply(e.getKey(), e.getValue()),
                e -> valueFun.apply(e.getKey(), e.getValue()),
                (oldVal, newVal) -> newVal,
                LinkedHashMap::new
        ));
    }


    public static <T extends Collection<E>, E> T split(String str, String delimiter, Class<E> clazz, Supplier<T> collSupplier) {
        T collection = collSupplier.get();
        if (isBlank(str)) {
            return collection;
        }
        for (String s : str.split(delimiter, 0)) {
            parseOpt(strOrNull(trim(s)), clazz).ifPresent(collection::add);
        }
        return collection;
    }

    public static <E> List<E> splitToList(String str, String delimiter, Class<E> clazz) {
        return split(str, delimiter, clazz, ArrayList::new);
    }

    public static List<String> splitToList(String str, String delimiter) {
        return split(str, delimiter, String.class, ArrayList::new);
    }

    public static List<String> splitToListByComma(String str) {
        return splitToList(str, ",");
    }

    public static <E> List<E> splitToListByComma(String str, Class<E> clazz) {
        return split(str, ",", clazz, ArrayList::new);
    }

    public static <E> Set<E> splitToSet(String str, String delimiter, Class<E> clazz) {
        return split(str, delimiter, clazz, HashSet::new);
    }

    public static <E> Set<E> splitToSetByComma(String str, Class<E> clazz) {
        return split(str, ",", clazz, HashSet::new);
    }

    /**
     * 集合按照步长分割成二维集合
     * 示例: splitList（[1,2,3],2）==> [[1, 2], [3]]
     */
    public static <E> List<List<E>> splitListToDyadicArray(List<E> l, int step) {
        if (Coding.isEmpty(l) || Coding.notMoreThanZero(step)) {
            return new ArrayList<>();
        }
        int rd = l.size() % step;
        int pn = l.size() / step + (rd == 0 ? 0 : 1);
        List<List<E>> ls = new ArrayList<>(pn);
        for (int i = 0; i < pn; i++) {
            if (pn == 1) {
                ls.add(l);
                break;
            }
            int end = (i + 1) * step;
            if (i == pn - 1) {
                end = l.size();
            }
            List<E> s = l.subList(i * step, end);
            ls.add(s);
        }
        return ls;
    }


    /**
     * char '\1'
     * 一般情况下不会和任何字符串重复了
     */
    private static final String CHAR_1 = String.valueOf('\1');

    public static List<String> splitToListByChar1(String str) {
        return splitToList(str, CHAR_1);
    }

    public static <E> List<E> splitToListByChar1(String str, Class<E> clazz) {
        return split(str, CHAR_1, clazz, ArrayList::new);
    }


    public static <T> String join(String delimiter, Iterable<T> os) {
        if (os == null) {
            return "";
        }
        StringJoiner joiner = new StringJoiner(delimiter);
        for (T o : os) {
            strOpt(o).ifPresent(joiner::add);
        }
        return joiner.toString();
    }

    @SafeVarargs
    public static <T> String join(String delimiter, T... os) {
        return join(delimiter, stream(os)::iterator);
    }

    public static <T> String joinByComma(Iterable<T> os) {
        return join(",", os);
    }

    @SafeVarargs
    public static <T> String joinByComma(T... os) {
        return join(",", stream(os)::iterator);
    }

    public static <T> String joinByChar1(Iterable<T> os) {
        return join(CHAR_1, os);
    }

    @SafeVarargs
    public static <T> String joinByChar1(T... os) {
        return join(CHAR_1, stream(os)::iterator);
    }


    /**
     * 将集装状态的字符串展开，和{@link #split(String, String, Class, Supplier) split}不同，展开操作不会过滤低价值数据
     *
     * @param str          压缩状态的字符串
     * @param delimiter    间隔符，使用正则，如果出现正则关键字符，需要在前边加转义\\
     * @param clazz        展开后每个元素的类型
     * @param collSupplier 提供集合
     * @param <T>          集合类型
     * @param <E>          元素类型
     */
    public static <T extends Collection<E>, E> T unwrap(String str, String delimiter, Class<E> clazz, Supplier<T> collSupplier) {
        T collection = collSupplier.get();
        if (isBlank(str)) {
            return collection;
        }
        for (String s : str.split(delimiter, -1)) {
            collection.add(parseOrNull(s, clazz));
        }
        return collection;
    }

    public static <T extends Collection<E>, E> T unwrapByComma(String str, Class<E> clazz, Supplier<T> collSupplier) {
        return unwrap(str, ",", clazz, collSupplier);
    }

    public static <E> List<E> unwrapByComma(String str, Class<E> clazz) {
        return unwrap(str, ",", clazz, ArrayList::new);
    }

    public static List<String> unwrapByComma(String str) {
        return unwrap(str, ",", String.class, ArrayList::new);
    }

    /**
     * 将多个元素打包成集装状态的字符串
     *
     * @param delimiter 间隔符
     * @param os        可迭代的元素们
     * @param <T>       元素类型
     */
    public static <T> String wrap(String delimiter, Iterable<T> os) {
        if (os == null) {
            return "";
        }
        StringJoiner joiner = new StringJoiner(delimiter);
        for (T o : os) {
            joiner.add(stringOrNull(o));
        }
        return joiner.toString();
    }

    @SafeVarargs
    public static <T> String wrap(String delimiter, T... os) {
        return wrap(delimiter, Arrays.stream(os)::iterator);
    }

    public static <T> String wrapByComma(Iterable<T> os) {
        return wrap(",", os);
    }

    @SafeVarargs
    public static <T> String wrapByComma(T... os) {
        return wrap(",", Arrays.stream(os)::iterator);
    }


    @SafeVarargs
    public static <T extends Number> T min(T... args) {
        switch (length(args)) {
            case 0:
                return null;
            case 1:
                return args[0];
            case 2:
                if (args[0] == null) {
                    return args[1];
                }
                if (args[1] == null) {
                    return args[0];
                }
                return args[0].doubleValue() < args[1].doubleValue() ? args[0] : args[1];
            default:
                return stream(args).min(Comparator.comparingDouble(Number::doubleValue)).orElse(args[0]);
        }
    }

    @SafeVarargs
    public static <T extends Number> T max(T... args) {
        switch (length(args)) {
            case 0:
                return null;
            case 1:
                return args[0];
            case 2:
                if (args[0] == null) {
                    return args[1];
                }
                if (args[1] == null) {
                    return args[0];
                }
                return args[0].doubleValue() > args[1].doubleValue() ? args[0] : args[1];
            default:
                return stream(args).max(Comparator.comparingDouble(Number::doubleValue)).orElse(args[0]);
        }
    }

    @SafeVarargs
    public static <T> T fluent(T o, Consumer<T>... consumers) {
        for (Consumer<T> consumer : consumers) {
            consumer.accept(o);
        }
        return o;
    }

    @SafeVarargs
    public static <T, R> T fluent(T o, BiConsumer<T, R> process, R... array) {
        stream(array).forEach(a -> process.accept(o, a));
        return o;
    }

    @SafeVarargs
    public static <T extends Collection<E>, E> T addAll(T collection, E... array) {
        collection.addAll(arrayList(array));
        return collection;
    }


}
